package cn.childin.auth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.security.SpringSessionBackedSessionRegistry;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import java.time.Duration;
import java.util.Arrays;

/**
 * Spring security Config
 *
 * @author zhangpeng
 * @since 2023/12/06 17:44
 */
@Configuration

/**
 * @EnableGlobalMethodSecurity配置在控制器方法上控制权限生效
 * 相关注解：@PreAuthorize、@PostAuthorize、@Secured：类似于 @PreAuthorize
 * 例子
 *  @PreAuthorize("principal.username.equals('javaboy')")
 *     public String hello() {
 *         return "hello";
 *     }
 *
 *     @PreAuthorize("hasRole('admin')")
 *     public String admin() {
 *         return "admin";
 *     }
 *
 *     @Secured({"ROLE_user"})
 *     public String user() {
 *         return "user";
 *     }
 *
 *     @PreAuthorize("#age>98")
 *     public String getAge(Integer age) {
 *         return String.valueOf(age);
 *     }
 */
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 基于内存配置账号密码
        auth.inMemoryAuthentication()
                .withUser("hello")
                .password("123")
                .roles("admin");
        auth.userDetailsService(userDetailsService());// 设置用户登录信息实现逻辑
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        // 配置忽略权限校验的url
        web.ignoring().antMatchers("/js/**", "/css/**", "/images/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated() // 配置所有登录用户都可以访问url
                .and()
                .formLogin()
                .loginPage("/login.html") // 配置登录页
                .loginProcessingUrl("/login") // 定义登录接口
                .defaultSuccessUrl("") // 登录成功跳转
                .successForwardUrl("") // 登录成功重定向
                .successHandler((request, response, authentication) -> {
                    System.out.println("登录成功");
                }) // 登录成功处理
                .failureHandler(((request, response, exception) -> {
                    System.out.println("登录失败");
                })) // 登录失败处理
                .permitAll() // 配置登录页面不需要被拦截
                .and()
                .csrf().disable() // 关闭csrf
                .exceptionHandling()
                .authenticationEntryPoint(((request, response, authException) -> {
                    System.out.println("未授权信息访问时处理逻辑");
                }))
                .and()
                .rememberMe() // 记住登录信息
                .key("remember-me-key") // 设置key
                .tokenRepository(persistentTokenRepository()) // 设置记住我持久化操作方式
                .and()
                .sessionManagement()
                .maximumSessions(1) // 设置同一个账号最大允许登录数
                .sessionRegistry(sessionRegistry()) // session管理
                .and()
                .and()
                .cors() // 配置跨域
                .configurationSource(configurationSource()) // 配置跨域策略，如果仅使用.cors()则表示所有跨域都支持
        ;
    }

    /**
     * 通过filter进行跨域全局配置
     * 主要解决OAuth2跨域访问问题
     * 在spring security中要允许OAuth2跨域需要配置一个filter，同时在HttpSecurity中允许跨域请求
     */
    @Bean
    public CorsFilter corsFilter() {
        CorsConfigurationSource source = configurationSource();
        return new CorsFilter(source);
    }

    /**
     * 配置跨域策略
     *
     * @return
     */
    @Bean
    public CorsConfigurationSource configurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowCredentials(true);
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("*"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setMaxAge(Duration.ofHours(1));
        source.registerCorsConfiguration("/**", configuration);

        return source;
    }

    /**
     * session管理，具体实现 RedisIndexedSessionRepository
     */
    @Autowired
    private FindByIndexNameSessionRepository findByIndexNameSessionRepository;

    @Bean
    public SpringSessionBackedSessionRegistry sessionRegistry() {
        return new SpringSessionBackedSessionRegistry(findByIndexNameSessionRepository);
    }

    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl();
        repository.setDataSource(null); // 配置DataSource
        return repository;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        // NoOpPasswordEncoder对密码不加密
        return NoOpPasswordEncoder.getInstance();
    }

    /**
     * 重写用户登录信息获取逻辑
     *
     * @return
     */
    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("zhangsan").password("123").roles("admin").build());
        return manager;
    }
}
